package com.smart.support.service;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson2.JSONObject;
import com.alicp.jetcache.Cache;
import com.smart.common.cache.CacheService;
import com.smart.common.entities.constant.SmartConstant;
import com.smart.common.entities.domain.AclMeta;
import com.smart.common.exception.SmartException;
import com.smart.manager.system.entities.enums.AccountState;
import com.smart.manager.system.entities.enums.LoginResult;
import com.smart.manager.system.entities.models.SystemAccount;
import com.smart.manager.system.entities.models.SystemLoginLog;
import com.smart.manager.system.entities.models.SystemMenu;
import com.smart.manager.system.entities.models.SystemRole;
import com.smart.manager.system.entities.models.query.QSystemAccount;
import com.smart.manager.system.entities.models.query.QSystemApi;
import com.smart.manager.system.entities.models.query.QSystemMenu;
import com.smart.manager.system.entities.models.query.QSystemRole;
import com.smart.manager.system.entities.views.SimpleAccount;
import com.smart.manager.system.entities.views.query.QSimpleAccount;
import com.smart.manager.system.repository.ConfigRepository;
import com.smart.support.bodys.ChangePswBody;
import com.smart.support.bodys.ManagerLoginBody;
import com.smart.utils.AclUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author Xiaxuqiu
 * @date 2023-04-01
 **/
@Service
@Log4j2
@RequiredArgsConstructor
public class AccountService {


    private final CacheService cacheService;
    private final ConfigRepository configRepository;

    public String accountLogin(ManagerLoginBody.AccountLoginBody body, HttpServletRequest request) {

        this.checkCaptchaCache(body);
        //当前已提交的登录次数
        int currentLoginCount = 0;
        JSONObject accountCfg = configRepository.readCache(SmartConstant.ACCOUNT_CFG);
        //是否启用安全策略
        boolean enableLoginCheck = accountCfg.getBooleanValue("enableLoginCheck");
        //失败次数
        int maxFailNum = accountCfg.getIntValue("maxFailNum");
        //锁定时长
        long lockDuration = accountCfg.getLongValue("lockDuration");
        if (enableLoginCheck) {
            Cache<String, Integer> accountLockCache = cacheService.getAccountLockCache();
            currentLoginCount = Optional.ofNullable(accountLockCache.get(body.getAccount())).orElse(0);
            //设置访问限制,密码超过最大次错误,限制自定义分钟内无法登录
            if (currentLoginCount >= maxFailNum) {
                throw new SmartException(String.format("当前账号登录失败错误过多,账号已被锁定登录 %s分钟", lockDuration));
            }
        }

        StringBuffer errMsg = new StringBuffer();
        SystemAccount account = new QSystemAccount().account.eq(body.getAccount()).setMaxRows(1).findOne();
        try {
            Assert.notNull(account, "用户名或密码错误");
            boolean check = DigestUtil.bcryptCheck(body.getPassword(), account.getPassword());
            Assert.isTrue(check, "登录密码错误!");
            Assert.notEquals(AccountState.PENDING, account.getState(), "当前账号尚未通过审核,请等待管理员审核后再进行登录");
            Assert.notEquals(AccountState.DISABLED, account.getState(), "当前账号已被禁用,禁止登录");
        } catch (Exception e) {
            saveLoginLog(body.getAccount(), LoginResult.FAIL, e.getMessage(), request);
            errMsg.append(e.getMessage());
        }

        if (ObjectUtil.isNotEmpty(errMsg)) {
            if (enableLoginCheck) {
                Cache<String, Integer> accountLockCache = cacheService.getAccountLockCache();
                accountLockCache.put(body.getAccount(), currentLoginCount + 1, lockDuration, TimeUnit.MINUTES);
                int remCount = maxFailNum - currentLoginCount;
                if (remCount > 0) {
                    errMsg.append(" 剩余重试次数:").append(remCount).append("次");
                }
            }
            throw new SmartException(errMsg.toString());
        }
        account.setLastVisitTime(LocalDateTime.now()).update();
        saveLoginLog(body.getAccount(), LoginResult.SUCCESS, "登录成功", request);
        return AclUtils.login(account.getId());
    }


    /**
     * 检查表单对象内的验证码
     *
     * @param body 身体
     */
    private void checkCaptchaCache(ManagerLoginBody.AccountLoginBody body) {
        //验证码检查
        if (StrUtil.isNotBlank(body.getUuid())) {
            log.warn(body);
            Cache<String, String> captchaCache = cacheService.getCaptchaCache();
            Assert.notBlank(body.getCaptcha(), "验证码不能为空");
            String cacheCaptcha = captchaCache.get(body.getUuid());
            Assert.notBlank(cacheCaptcha, "验证码已过期或已失效");
            Assert.equals(cacheCaptcha, body.getCaptcha().toUpperCase(), "验证码错误");
        }
    }


    /**
     * 获取账户简单信息
     *
     * @return {@code SystemAccount}
     */
    public SimpleAccount getSimpleAccount() {
        Long loginId = AclUtils.getLoginId();

        SimpleAccount account = new QSimpleAccount().id.eq(loginId)
                .state.eq(AccountState.NORMAL).setMaxRows(1).findOne();
        Assert.notNull(account, "账号信息查询失败");
        Assert.equals(account.getState(), AccountState.NORMAL, "当前账号不可用");
        return account;
    }


    /**
     * 获取当前账号的ACL资源信息
     *
     * @return {@code SimpleAccount}
     */
    public AclMeta getAccountAcl() {
        AclMeta aclMeta1 = AclUtils.getAclMeta();
        if (ObjectUtil.isNotEmpty(aclMeta1)) {
            return aclMeta1;
        }

        Long loginId = AclUtils.getLoginId();
        SystemAccount account = new QSystemAccount().id.eq(loginId).setMaxRows(1).findOne();
        Assert.notNull(account, "账号信息不存在");
        Assert.notEquals(AccountState.PENDING, account.getState(), "当前账号尚未通过审核,请等待管理员审核后再进行登录");
        Assert.notEquals(AccountState.DISABLED, account.getState(), "当前账号已被禁用,禁止登录");
        AclMeta aclMeta = new AclMeta();
        aclMeta.setAccount(account.getAccount()).setId(account.getId()).setName(account.getName()).setOrganCode(account.getOrgan().getCode())
                .setDataScope(account.getDataScope()).setDeptCode(account.getDept().getCode())
                .setRoleCodes(Set.copyOf(account.getRoleCodes()));
        fillMenuIdsAndExpr(aclMeta);
        AclUtils.setAclMeta(aclMeta);
        return aclMeta;

    }


    public void fillMenuIdsAndExpr(AclMeta aclMeta) {
        List<SystemRole> allRole = new QSystemRole().code.in(aclMeta.getRoleCodes()).findList();
        Assert.notEquals(allRole, "账号角色查询失败");
        Optional<SystemRole> hasSupperRole = allRole.stream()
                .filter(x -> x.getSupperRole().equals(true)).findFirst();
        hasSupperRole.ifPresentOrElse(x -> fillForSupper(aclMeta), () -> fillForRoles(aclMeta, allRole));
    }


    /**
     * 填充有拥有超级管理员角色的权限
     *
     * @param aclMeta acl元
     */
    private void fillForSupper(AclMeta aclMeta) {
        aclMeta.setSupperRole(true);
        aclMeta.setMenuIds(Collections.emptySet());
        // 填充了权限表达ID
        List<String> exprList = new QSystemApi().select(QSystemApi.Alias.expr).findSingleAttributeList();
        aclMeta.setApiExprList(Set.copyOf(exprList));
    }


    /**
     * 按具体角色填充Acl数据
     *
     * @param aclMeta  acl元
     * @param roleList 角色列表
     */
    private void fillForRoles(AclMeta aclMeta, List<SystemRole> roleList) {

        aclMeta.setSupperRole(false);
        HashSet<Long> menuIds = new HashSet<>();
        HashSet<String> tokens = new HashSet<>();
        // 填充菜单
        roleList.forEach(x -> {
            menuIds.addAll(x.getMenuIds());
            tokens.addAll(x.getApiTokens());
        });

        // 填充了权限表达ID
        List<String> exprList = new QSystemApi().token.in(tokens).select(QSystemApi.Alias.expr).findSingleAttributeList();
        aclMeta.setApiExprList(Set.copyOf(exprList));
        aclMeta.setMenuIds(Set.copyOf(menuIds));
    }


    public void fillMenuList(AclMeta accountAcl) {
        QSystemMenu qSystemMenu = new QSystemMenu();
        if (!accountAcl.getSupperRole()) {
            qSystemMenu.id.in(accountAcl.getMenuIds());
        }
        List<SystemMenu> menuList = qSystemMenu.order().sort.asc().findList();
        accountAcl.setMenuList(menuList);
    }

    public void changePsw(ChangePswBody body) {
        Long loginId = AclUtils.getLoginId();
        SystemAccount systemAccount = new QSystemAccount().id.eq(loginId).findOne();
        Assert.notNull(systemAccount, "账号信息获取失败");
        boolean check = DigestUtil.bcryptCheck(body.getOldPasswd(), systemAccount.getPassword());
        Assert.isTrue(check, "登录密码错误!");
        String bcrypt = DigestUtil.bcrypt(body.getNewPasswd());
        systemAccount.setPassword(bcrypt);
        systemAccount.update();
    }


    /**
     * 保存登录日志
     */
    @Async
    public void saveLoginLog(String account, LoginResult result, String description, HttpServletRequest request) {
        if (ObjectUtil.isEmpty(request)) {
            return;
        }
        //不记录查询日志
        if (RequestMethod.GET.name().equalsIgnoreCase(request.getMethod())) {
            return;
        }
        String userAgentStr = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
        String clientIp = ServletUtil.getClientIP(request);
        String ip = "0:0:0:0:0:0:0:1".equals(clientIp) ? "127.0.0.1" : clientIp;
        new SystemLoginLog().setAccount(account)
                .setDescription(description)
                .setIp(ip).setResult(result)
                .setBrowser(userAgent.getBrowser().getName() + " " + userAgent.getBrowser().getVersion(userAgentStr))
                .setOs(userAgent.getOs().getName())
                .setLoginTime(LocalDateTime.now())
                .save();
    }


}
